package weeny.pre.face.treetable;

import java.awt.event.MouseEvent;
import java.util.Collection;

import javax.swing.JLabel;

import prefuse.data.Tuple;
import prefuse.data.column.Column;
import prefuse.data.parser.DataParseException;
import prefuse.data.parser.DataParser;
import prefuse.data.parser.ObjectParser;
import weeny.util.logs.Debug;

/**
 * AbstractTreeNode is used in jtree. So every node should inherit this.
 * 
 * @author weeny
 */
public abstract class AbstractTreeNode{
	public static final String UNAVAILABLE = "N/A";
	public static final ObjectParser READ_ONLY_PARSER = new ObjectParser();
	
	public final String LABEL_FIELD;
	public final String str;		
	public final Object obj;
	
	/**
	 * By default, this class has two memeber variables. You can use them for
	 * any purpose. Just make sure the following abstract functions work fine.
	 */
	public AbstractTreeNode(String labelField, String str, Object obj) {
		this.LABEL_FIELD = labelField;
		this.str = str;
		this.obj = obj;
	}
	
	/**
	 * Add a child to this node, the behavior should be defined by overriding.
	 * This is called in {@link PreJTreeTable#wrap(java.awt.Dimension)} 
	 */
	boolean addChild(boolean altDown){
		Debug.info("Ignored adding child to "+this.getClass());
		return false;
	}
	
	/**
	 * Get the type in this field, return null if the field does not exist.
	 */
	abstract public Class getType(String field);
	/**
	 * Define what text to show in the tree view. This function is called before
	 * {@link #setTreeIcon(JLabel, boolean)}. So there are two ways to change
	 * the tree labels
	 */
	abstract public String toString();
	/**
	 * Delegator of {@link AbstractPreModel#getChildCount(Object)}
	 */
	abstract public int getChildCount();
	/**
	 * Delegator of {@link AbstractPreModel#getChild(Object, int)}
	 */
	abstract public AbstractTreeNode getChild(int idx);
	/**
	 * Delegator of {@link AbstractPreModel#isCellEditable(Object, int)}
	 */
	abstract public boolean isCelleEditable(String field);
	/**
	 * Delegator of {@link AbstractPreModel#getValueAt(Object, int)}. The return
	 * value will be directly passed to the jtable or jtree. Boolean and String
	 * are most common, so you probably use {@link #toView(DataParser, Object)} 
	 */
	abstract public Object toView(String field);
	/**
	 * Delegator of {@link AbstractPreModel#setValueAt(Object, Object, int)}.
	 * This function should directly modify the backend data table. The view
	 * parameter is comming from jTable, which are usually Boolean or String.
	 * You can use {@link #toData(Tuple, Object, String)}. The return value
	 * indicates whether the actual data is changed! (NOT indicating whether
	 * the operation is successfull.)
	 */
	abstract public boolean toData(Object view, String field);

	/**
	 * When the abstract tree node is clicked, this function will be called, so
	 * if you want to handle extra actions for mouse events, please override
	 * this function. For example, see {@link FieldNode#nodeClicked(MouseEvent)}
	 * 
	 * @return return true if you need to refresh the whole table UI
	 */
	public boolean nodeClicked(MouseEvent me){
		//do nothing.
		return false;
	}
	
	/**
	 * Reset the tree icon, the one passed-in is using system default icon. This
	 * func is used in {@link AbstractPreModel#getTreeCellRendererComponent()}.
	 * It is the last step befor used by the system, so you can virtually do
	 * anything (not limited to icons) to this label, and the effects will be 
	 * seen in the interface.
	 */
	public void setTreeIcon(JLabel label, boolean expanded){
		//do nothing; use system default
	}
	
	/**
	 * Remove the item itself, which is invoked by users selecting and clicking
	 * remove button. But it is ok to igonre it, just like this one. The "hard"
	 * means that users click the remove button with alt key held. Anyway, it is
	 * just another state of remove operation.
	 */
	public boolean remove(boolean altDown){
		Debug.info("Ignore remove operation, do not know how~");
		return false;
	}
	
	/**
	 * Handle the event of drop, all the nodes are dropped on this node. So this
	 * node should decide how to handle them.
	 */
	public boolean dropNodes(Collection<AbstractTreeNode> coll){
		Debug.info(coll.size()+" nodes are dropped, but ignored");
		return false;
	}
	
	
	//-------------------------------------------------------------------------
	//helper functions for further uses
	/**
	 * Helper function to format the field value to the displayed value
	 */
	public static Object toView(DataParser dp, Object data){
		if (data == null) {
			return "";
		}
		if (dp.getType() == Boolean.class || dp.getType() == boolean.class) {
			if (data instanceof String) {
				return Boolean.parseBoolean(data.toString());
			}
			return data;
		}
		else if (!(dp instanceof ObjectParser)){
			return dp.format(data);
		}
		return data;
	}
	
	/**
	 * Helper function to parse the displayed value to the field value, and
	 * actually place it in the tuple. Please notice that we assume that view
	 * is either Boolean or String.
	 */
	public static boolean toData(Tuple t, Object view, String field){
		Column col = t.getTable().getColumn(field);
		Object data = t.get(field);
		if (col.canGetBoolean()) {
			if (view instanceof Boolean) {
				data = view;
			}
			else {
				data = Boolean.parseBoolean(view.toString());
			}
		}
		else if (!(col.getParser() instanceof ObjectParser)){
			try {
				data =  col.getParser().parse(view.toString());
			} catch (DataParseException e) {
				if (view.toString().length() == 0) {
					Debug.info("Setting data to null (String length is zero)");
					data = null;
				}
				else{
					Debug.prob("Cannot parse: "+view+", use previous value");
				}
			}
		}
		else {
			Debug.prob("Got a String for ObjectParser!");
			data = view;
		}
		//now data is what you need to set
		try{
			Object oldVal = t.get(field);
			if (oldVal == null || !oldVal.equals(data)){
				t.set(field, data);
				Debug.info("Setting ["+field+"] with value = "+data);
				return true;
			}
			else if (oldVal != null && oldVal.equals(data)){
				Debug.info("Ignore setting ["+field+"] (SAME): "+data);
				return false;
			}
		}
		catch (Exception e) {
			Debug.prob("cannot set the value "+data+" to ["+field+"]");
			return false;
		}
		return false;
	}
}